import ftplib
import os
import time
from ftplib import FTP

class FtpSync:
    def __init__(self, host, username, password, local_dir, remote_dir):
        """
        初始化FTP同步类
        :param host: FTP服务器地址
        :param username: FTP用户名
        :param password: FTP密码
        :param local_dir: 本地目录路径
        :param remote_dir: 远程目录路径
        """
        self.host = host
        self.username = username
        self.password = password
        self.local_dir = local_dir
        self.remote_dir = remote_dir
        self.ftp = None
        
    def connect(self):
        """
        连接到FTP服务器
        :return: 是否连接成功
        """
        try:
            self.ftp = FTP(self.host)
            self.ftp.login(self.username, self.password)
            print(f'成功连接到FTP服务器: {self.host}')
            return True
        except Exception as e:
            print(f'连接FTP服务器失败: {e}')
            return False
    
    def disconnect(self):
        """
        断开FTP连接
        """
        if self.ftp:
            try:
                self.ftp.quit()
                print('已断开FTP连接')
            except:
                # 忽略可能的异常
                pass
    
    def upload_db_file(self, local_db_path=None, remote_db_path=None):
        """
        上传并覆盖FTP服务器上的yetian.db文件
        :param local_db_path: 本地yetian.db文件路径，如果不提供则使用默认路径
        :param remote_db_path: 远程yetian.db文件路径，如果不提供则使用默认路径
        :return: 是否上传成功
        """
        # 设置默认路径
        if local_db_path is None:
            local_db_path = os.path.dirname(os.path.abspath(__file__))+'/yetian.db'
            # local_db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'yetian.db')
            
        if remote_db_path is None:
            remote_db_path = '/spider3/yetian.db'
            
        # 确保本地文件存在
        if not os.path.exists(local_db_path):
            print(f'本地数据库文件不存在: {local_db_path}')
            return False
            
        # 连接到FTP服务器
        if not self.connect():
            print('无法连接到FTP服务器，上传数据库文件失败')
            return False
            
        try:
            print(f'开始上传数据库文件: {local_db_path} -> {remote_db_path}')
            start_time = time.time()
            
            # 保存当前工作目录
            current_dir = self.ftp.pwd()
            
            try:
                # 使用STOR命令上传文件（会覆盖已存在的文件）
                with open(local_db_path, 'rb') as file:
                    self.ftp.storbinary(f'STOR {remote_db_path}', file)
                
                end_time = time.time()
                print(f'成功上传并覆盖数据库文件: {remote_db_path}，耗时: {end_time - start_time:.2f} 秒')
                return True
            except Exception as e:
                print(f'上传数据库文件失败: {e}')
                return False
            finally:
                # 恢复到原始工作目录
                try:
                    self.ftp.cwd(current_dir)
                except:
                    pass
            
        finally:
            # 确保断开连接
            self.disconnect()
                
    def _create_remote_directory_recursive(self, remote_path):
        """
        递归创建远程目录结构
        :param remote_path: 要创建的远程目录路径
        :return: 是否创建成功
        """
        try:
            # 保存当前工作目录
            current_dir = self.ftp.pwd()
            
            # 分解路径
            path_parts = remote_path.split('/')
            
            # 处理绝对路径
            if path_parts[0] == '':
                # 如果是绝对路径，从根目录开始
                self.ftp.cwd('/')
                path_parts = path_parts[1:]
            
            # 递归创建每个目录
            for part in path_parts:
                if part:
                    try:
                        # 尝试切换到目录
                        self.ftp.cwd(part)
                        print(f'已存在目录: {part}')
                    except ftplib.error_perm as e:
                        if '550' in str(e):  # 目录不存在
                            # 创建目录
                            self.ftp.mkd(part)
                            print(f'创建目录: {part}')
                            # 切换到新创建的目录
                            self.ftp.cwd(part)
                        else:
                            print(f'创建目录 {part} 失败: {e}')
                            return False
            
            # 恢复当前工作目录
            self.ftp.cwd(current_dir)
            print(f'成功创建完整目录结构: {remote_path}')
            return True
        except Exception as e:
            print(f'递归创建目录结构失败: {e}')
            # 尝试恢复当前工作目录
            try:
                self.ftp.cwd(current_dir)
            except:
                pass
            return False
    
    def create_remote_directory(self, remote_path):
        """
        创建远程目录（简化版）
        :param remote_path: 要创建的远程目录路径
        """
        try:
            # 尝试切换到目录，如果成功则说明目录已存在
            self.ftp.cwd(remote_path)
            print(f'远程目录已存在: {remote_path}')
            return True
        except ftplib.error_perm as e:
            if '550' in str(e):  # 目录不存在错误代码
                try:
                    # 尝试创建目录
                    self.ftp.mkd(remote_path)
                    print(f'创建远程目录: {remote_path}')
                    # 切换到新创建的目录
                    self.ftp.cwd(remote_path)
                    return True
                except Exception as mkd_error:
                    print(f'创建远程目录 {remote_path} 失败: {mkd_error}')
                    # 如果创建失败，不要抛出异常，让调用者决定如何处理
                    return False
            else:
                print(f'访问远程目录 {remote_path} 失败: {e}')
                return False
        except Exception as e:
            print(f'创建远程目录失败: {e}')
            return False
    
    def upload_file(self, local_file_path, remote_file_path):
        """
        上传单个文件，确保保留目录结构
        :param local_file_path: 本地文件路径
        :param remote_file_path: 远程文件路径
        :return: 是否上传成功
        """
        try:
            # 确保本地文件存在
            if not os.path.exists(local_file_path):
                print(f'本地文件不存在: {local_file_path}')
                return False
            
            # 保存当前工作目录，以便上传后恢复
            current_dir = self.ftp.pwd()
            
            try:
                # 优先尝试方式2: 使用完整路径上传
                print(f'尝试使用完整路径上传文件: {remote_file_path}')
                with open(local_file_path, 'rb') as file:
                    self.ftp.storbinary(f'STOR {remote_file_path}', file)
                print(f'成功上传文件: {local_file_path} -> {remote_file_path}')
                return True
            except Exception as e2:
                print(f'使用完整路径上传失败: {e2}')
                
                # 尝试方式3: 分解路径，手动创建目录结构并上传
                try:
                    # 获取文件名
                    file_name = os.path.basename(remote_file_path)
                    # 获取目录路径
                    remote_dir = os.path.dirname(remote_file_path)
                    
                    print(f'尝试创建目录结构 {remote_dir} 并上传文件 {file_name}')
                    
                    # 递归创建所有必要的目录
                    if remote_dir and remote_dir != '.':
                        self._create_remote_directory_recursive(remote_dir)
                        
                    # 切换到目标目录
                    self.ftp.cwd(remote_dir)
                    print(f'已切换到目录: {remote_dir}')
                        
                    # 上传文件
                    with open(local_file_path, 'rb') as file:
                        self.ftp.storbinary(f'STOR {file_name}', file)
                    print(f'成功上传文件: {local_file_path} -> {remote_file_path}')
                    return True
                except Exception as e3:
                    print(f'创建目录结构后上传失败: {e3}')
                    
                    # 作为最后的尝试，尝试方式1: 直接上传到当前目录
                    try:
                        file_name = os.path.basename(local_file_path)
                        print(f'最后尝试直接上传文件 {file_name} 到当前目录')
                        with open(local_file_path, 'rb') as file:
                            self.ftp.storbinary(f'STOR {file_name}', file)
                        print(f'成功上传文件: {local_file_path} -> {file_name} (但可能未保留目录结构)')
                        return True
                    except Exception as e1:
                        print(f'所有上传方式均失败: {e1}')
                        return False
            finally:
                # 恢复到原始工作目录
                try:
                    self.ftp.cwd(current_dir)
                except:
                    pass
        except Exception as e:
            print(f'上传文件过程中发生错误 {local_file_path}: {e}')
            return False
    
    def sync_directories(self):
        """
        同步本地目录到远程目录
        :return: 同步结果
        """
        if not self.connect():
            return False
        
        total_files = 0
        uploaded_files = 0
        failed_files = 0
        
        try:
            # 确保本地目录存在
            if not os.path.exists(self.local_dir):
                print(f'本地目录不存在: {self.local_dir}')
                return False
            
            # 确保远程根目录存在
            try:
                self.create_remote_directory(self.remote_dir)
            except Exception as root_dir_error:
                print(f'创建远程根目录失败: {root_dir_error}')
                # 尝试切换到远程根目录，看是否存在
                try:
                    self.ftp.cwd(self.remote_dir)
                    print(f'远程根目录已存在: {self.remote_dir}')
                except:
                    print(f'无法访问远程根目录: {self.remote_dir}')
                    return False
            
            # 遍历本地目录
            for root, dirs, files in os.walk(self.local_dir):
                for file in files:
                    total_files += 1
                    
                    try:
                        # 构建本地文件完整路径
                        local_file_path = os.path.join(root, file)
                        
                        # 构建远程文件路径
                        # 计算相对路径
                        relative_path = os.path.relpath(local_file_path, self.local_dir)
                        # 先处理路径分隔符，再拼接字符串
                        formatted_relative_path = relative_path.replace('\\', '/')
                        
                        # 确保远程路径格式正确（避免双斜杠）
                        remote_file_path = f"{self.remote_dir.rstrip('/')}/{formatted_relative_path.lstrip('/')}"
                        
                        # 上传文件
                        if self.upload_file(local_file_path, remote_file_path):
                            uploaded_files += 1
                        else:
                            failed_files += 1
                    except Exception as file_error:
                        failed_files += 1
                        print(f'处理文件 {file} 时出错: {file_error}')
                        # 继续处理下一个文件
                        continue
                
            print(f'同步完成！总共 {total_files} 个文件，成功上传 {uploaded_files} 个，失败 {failed_files} 个。')
            return True
        except Exception as e:
            print(f'同步过程中发生错误: {e}')
            return False
        finally:
            # 确保断开连接
            self.disconnect()

def upload_news_file():
    """
    执行FTP同步，上传新闻的图片和详情文本文件
    """
    # FTP服务器配置
    HOST = '39.105.168.205'  # 用户配置的实际FTP服务器地址
    USERNAME = 'pdf_of'
    PASSWORD = 'P9N7dWZ4wCYT'
    
    # 目录配置
    LOCAL_DIR = 'E:/trae_project/spider3/upload/'
    REMOTE_DIR = '/spider3/upload/'
    
    # 创建同步实例
    ftp_sync = FtpSync(HOST, USERNAME, PASSWORD, LOCAL_DIR, REMOTE_DIR)
    
    # 执行同步
    start_time = time.time()
    success = ftp_sync.sync_directories()
    end_time = time.time()
    
    if success:
        print(f'同步成功完成，耗时: {end_time - start_time:.2f} 秒')
    else:
        print('同步失败')

def upload_db_file():
    """
    执行FTP同步，上传新闻的数据库文件yetian.db
    """
    # FTP服务器配置
    HOST = '127.0.0.0'  # 用户配置的实际FTP服务器地址
    USERNAME = 'pdf_of'
    PASSWORD = '123456'
    # 初始化FTP同步类
    ftp_sync = FtpSync(
        host=HOST,
        username=USERNAME,
        password=PASSWORD,
        local_dir='',
        remote_dir='/'
    )

    # 使用新方法上传数据库文件
    # 可选择自定义本地和远程路径，或使用默认路径
    success = ftp_sync.upload_db_file()
    # 或 success = ftp_sync.upload_db_file(local_db_path='custom_path/yetian.db', remote_db_path='/custom/path/yetian.db')

    if success:
        print('数据库文件上传成功')
    else:
        print('数据库文件上传失败')



if __name__ == '__main__':
    upload_news_file()
    upload_db_file()
